﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;
using System.ComponentModel;

namespace Watcher.Matrix
{

    public static class ClassExt
    {
        public static void CheckNull<T>(this T value, string paramName)
   where T : class
        {
            if (value == null) throw new ArgumentNullException(paramName);
        }
    }

    public enum MatrixOrientation
    {
        FixedWidth, FixedHeight
    }

    public class MatrixChangedEventArgs : EventArgs
    {
        private readonly int x, y;
        public int X { get { return x; } }
        public int Y { get { return y; } }
        public MatrixChangedEventArgs(int x, int y)
        {
            this.x = x;
            this.y = y;
        }
    }

    public class Matrix<T> : IListSource
    {
        public override string ToString()
        {
            return string.Format("{0}[{1},{2}]", typeof(T).Name, Width,
   Height);
        }
        public event EventHandler<MatrixChangedEventArgs> ValueChanged;
        protected void OnValueChanged(int x, int y)
        {
            if (ValueChanged != null)
            {
                ValueChanged(this, new MatrixChangedEventArgs(x, y));
            }
        }
        private readonly int fixedSize;
        private readonly MatrixOrientation orientation;
        public MatrixOrientation Orientation { get { return orientation; } }
        private readonly List<T> list;
        public Matrix(MatrixOrientation orientation, int width, int height)
        {
            switch (orientation)
            {
                case MatrixOrientation.FixedHeight:
                    fixedSize = height;
                    break;
                case MatrixOrientation.FixedWidth:
                    fixedSize = width;
                    break;
                default:
                    throw new ArgumentOutOfRangeException("orientation");
            }
            this.orientation = orientation;
            CheckDimension(width, orientation ==
   MatrixOrientation.FixedWidth, "width");
            CheckDimension(height, orientation ==
   MatrixOrientation.FixedWidth, "height");
            list = new List<T>(width * height);
            AddRange(orientation == MatrixOrientation.FixedWidth ? height :
   width);
        }
        public void TrimExcess()
        {
            list.TrimExcess();
        }
        private void CheckDimension(int size, bool disallowEqual, string
   paramName)
        {
            if (size < 0) throw new ArgumentOutOfRangeException(paramName,
   "Dimension cannot be negative");
            if (size == 0 && disallowEqual) throw new
   ArgumentOutOfRangeException(paramName, "Dimension cannot be zero");
        }
        public Matrix(MatrixOrientation orientation, int fixedSize)
            : this(orientation,
            orientation == MatrixOrientation.FixedWidth ? fixedSize : 0,
            orientation == MatrixOrientation.FixedHeight ? fixedSize : 0)
        {
        }
        public void Clear()
        {
            list.Clear();
        }
        private int Linearize(int x, int y)
        {
            if (orientation == MatrixOrientation.FixedWidth)
            {
                if (x < 0 || y < 0 || x >= fixedSize) throw new
   ArgumentOutOfRangeException();
                return (y * fixedSize) + x;
            }
            else
            {
                if (y < 0 || y < 0 || y >= fixedSize) throw new
   ArgumentOutOfRangeException();
                return (x * fixedSize) + y;
            }
        }
        public T this[int x, int y]
        {
            get
            {
                return list[Linearize(x, y)];
            }
            set
            {
                list[Linearize(x, y)] = value;
                OnValueChanged(x, y);
            }
        }
        public void Add() { AddRange(1); }
        public void Add(T[] values)
        {
            values.CheckNull("values");
            if (values.Length != fixedSize) throw new
   ArgumentException("Array must be the same size as the fixed dimension",
   "values");
            list.AddRange(values);
        }
        public void AddRange(int count)
        {
            CheckDimension(count, false, "count");
            count *= fixedSize;
            while (count-- > 0)
            {
                list.Add(default(T));
            }
        }
        public void Remove() { RemoveRange(1); }
        public void RemoveRange(int count)
        {
            CheckDimension(count, false, "count");
            count *= fixedSize;
            list.RemoveRange(list.Count - count, count);
        }
        public int FixedSize { get { return fixedSize; } }
        public int DynamicSize { get { return list.Count / fixedSize; } }
        public int Width
        {
            get
            {
                return orientation == MatrixOrientation.FixedWidth
                ? FixedSize : DynamicSize;
            }
        }
        public int Height
        {
            get
            {
                return orientation == MatrixOrientation.FixedHeight
                ? FixedSize : DynamicSize;
            }
        }
        #region IListSource Members
        bool IListSource.ContainsListCollection
        {
            get { return false; }
        }
        System.Collections.IList IListSource.GetList()
        {
            return DefaultView;
        }
        #endregion
        private MatrixView<T> defaultView;
        public MatrixView<T> DefaultView
        {
            get
            {
                if (defaultView == null)
                {
                    defaultView = new MatrixView<T>(this, false);
                }
                return defaultView;
            }
        }
    }

    public class MatrixView<T> : ITypedList, IList<MatrixViewRow<T>>, IList,
    IBindingList
    {
        private readonly Matrix<T> parent;
        private readonly bool transposed;
        public MatrixView<T> Transpose()
        {
            return new MatrixView<T>(parent, !transposed);
        }
        internal MatrixView(Matrix<T> parent, bool transposed)
        {
            parent.CheckNull("parent");
            this.parent = parent;
            this.transposed = transposed;
        }
        public PropertyDescriptorCollection GetColumns()
        {
            int width = Width;
            List<PropertyDescriptor> props = new
   List<PropertyDescriptor>(width);
            for (int i = 0; i < width; i++)
            {
                props.Add(new MatrixViewCol<T>(this, i));
            }
            return new PropertyDescriptorCollection(props.ToArray(), true);
        }
        PropertyDescriptorCollection
   ITypedList.GetItemProperties(PropertyDescriptor[] listAccessors)
        {
            if (listAccessors != null && listAccessors.Length != 0)
            {
                throw new NotSupportedException();
            }
            return GetColumns();
        }
        string ITypedList.GetListName(PropertyDescriptor[] listAccessors)
        {
            return "";
        }
        public void Clear()
        {
            parent.Clear();
        }
        public int IndexOf(MatrixViewRow<T> slice)
        {
            slice.CheckNull("slice");
            return slice.Index;
        }
        void IList<MatrixViewRow<T>>.Insert(int index, MatrixViewRow<T> slice)
        {
            throw new NotSupportedException();
        }
        void IList<MatrixViewRow<T>>.RemoveAt(int index)
        {
            throw new NotSupportedException();
        }
        bool ICollection<MatrixViewRow<T>>.Contains(MatrixViewRow<T> slice)
        {
            if (slice == null) return false;
            return slice.Index >= 0 && slice.Index < Height;
        }
        public int Width
        {
            get { return transposed ? parent.Height : parent.Width; }
        }
        public int Height
        {
            get { return transposed ? parent.Width : parent.Height; }
        }
        public MatrixViewRow<T> this[int index]
        {
            get { return new MatrixViewRow<T>(this, index); }
            set { throw new NotSupportedException(); }
        }
        void ICollection<MatrixViewRow<T>>.Add(MatrixViewRow<T> slice)
        {
            throw new NotSupportedException();
        }
        bool ICollection<MatrixViewRow<T>>.Remove(MatrixViewRow<T> slice)
        {
            throw new NotSupportedException();
        }
        public int Count { get { return Height; } }
        bool ICollection<MatrixViewRow<T>>.IsReadOnly { get { return false; } }
        bool IList.IsReadOnly { get { return ThisList.IsReadOnly; } }
        bool IList.IsFixedSize { get { return true; } }
        public IEnumerator<MatrixViewRow<T>> GetEnumerator()
        {
            for (int i = 0; i < Height; i++)
            {
                yield return this[i];
            }
        }
        IEnumerator IEnumerable.GetEnumerator()
        {
            return GetEnumerator();
        }
        int IList.Add(object obj)
        {
            MatrixViewRow<T> slice = (MatrixViewRow<T>)obj;
            ThisList.Add(slice);
            return slice.Index;
        }
        bool IList.Contains(object obj)
        {
            return ThisList.Contains((MatrixViewRow<T>)obj);
        }
        int IList.IndexOf(object obj)
        {
            return IndexOf((MatrixViewRow<T>)obj);
        }
        void IList.Remove(object obj)
        {
            ThisList.Remove((MatrixViewRow<T>)obj);
        }
        private IList<MatrixViewRow<T>> ThisList { get { return this; } }
        void IList.Insert(int index, object obj)
        {
            ThisList.Insert(index, (MatrixViewRow<T>)obj);
        }
        void IList.RemoveAt(int index)
        {
            ThisList.RemoveAt(index);
        }
        object IList.this[int index]
        {
            get { return this[index]; }
            set { this[index] = (MatrixViewRow<T>)value; }
        }
        object ICollection.SyncRoot
        {
            get { return parent; }
        }
        bool ICollection.IsSynchronized
        {
            get { return false; }
        }
        void ICollection.CopyTo(Array values, int index)
        {
            for (int i = 0; i < Height; i++)
            {
                values.SetValue(this[i], i + index);
            }
        }
        void ICollection<MatrixViewRow<T>>.CopyTo(MatrixViewRow<T>[]
   values, int index)
        {
            for (int i = 0; i < Height; i++)
            {
                values[i + index] = this[i];
            }
        }
        public bool IsTransposed { get { return transposed; } }
        public string RowName(int index)
        {
            return (IsTransposed ? "Col" : "Row") + index.ToString();
        }
        public string ColName(int index)
        {
            return (IsTransposed ? "Row" : "Col") + index.ToString();
        }
        public T this[int colIndex, int rowIndex]
        {
            get
            {
                return IsTransposed ? parent[rowIndex, colIndex] :
                    parent[colIndex, rowIndex];
            }
            set
            {
                if (IsTransposed)
                {
                    parent[rowIndex, colIndex] = value;
                }
                else
                {
                    parent[colIndex, rowIndex] = value;
                }
            }
        }
        bool IBindingList.AllowNew { get { return false; } }
        bool IBindingList.AllowRemove { get { return false; } }
        bool IBindingList.AllowEdit { get { return true; } }
        object IBindingList.AddNew()
        {
            throw new NotSupportedException();
        }
        void IBindingList.AddIndex(PropertyDescriptor property) { }
        void IBindingList.RemoveIndex(PropertyDescriptor property) { }
        bool IBindingList.SupportsSorting { get { return false; } }
        void IBindingList.ApplySort(PropertyDescriptor property,
   ListSortDirection direction)
        {
            throw new NotSupportedException();
        }
        PropertyDescriptor IBindingList.SortProperty { get { return null; } }
        ListSortDirection IBindingList.SortDirection
        {
            get
            {
                return
                    ListSortDirection.Ascending;
            }
        }
        bool IBindingList.IsSorted { get { return false; } }
        void IBindingList.RemoveSort() { }
        bool IBindingList.SupportsSearching { get { return false; } }
        int IBindingList.Find(PropertyDescriptor property, object obj)
        {
            throw new NotSupportedException();
        }
        bool IBindingList.SupportsChangeNotification { get { return true; } }
        private ListChangedEventHandler listChanged;
        public event ListChangedEventHandler ListChanged
        {
            add
            {
                bool first = listChanged == null;
                listChanged += value;
                if (first && listChanged != null) parent.ValueChanged +=
   ListChangedHandler;
            }
            remove
            {
                listChanged -= value;
                if (listChanged == null) parent.ValueChanged -=
   ListChangedHandler;
            }
        }
        private void OnListChanged(int index)
        {
            if (listChanged != null)
            {
                listChanged(this, new
   ListChangedEventArgs(ListChangedType.ItemChanged, index));
            }
        }
        private void OnRowChanged(int index)
        {
            EventHandler handler;
            if (rowHandlers != null && rowHandlers.TryGetValue(index, out 
handler))
            {
                handler(this, EventArgs.Empty);
            }
        }
        private void ListChangedHandler(object sender,
   MatrixChangedEventArgs args)
        {
            int index = IsTransposed ? args.X : args.Y;
            OnListChanged(index);
        }
        private void RowChangedHandler(object sender,
   MatrixChangedEventArgs args)
        {
            int index = IsTransposed ? args.X : args.Y;
            OnRowChanged(index);
        }
        internal void AddRowHandler(int index, EventHandler handler)
        {
            if (handler == null) return;
            if (rowHandlers == null)
            {
                rowHandlers = new Dictionary<int, EventHandler>();
                parent.ValueChanged += RowChangedHandler;
            }
            EventHandler value;
            rowHandlers.TryGetValue(index, out value);
            value += handler;
            rowHandlers[index] = value;
        }
        internal void RemoveRowHandler(int index, EventHandler handler)
        {
            if (handler == null || rowHandlers == null) return;
            EventHandler value;
            if (rowHandlers.TryGetValue(index, out value))
            {
                value -= handler;
                if (value == null)
                {
                    rowHandlers.Remove(index);
                    if (rowHandlers.Count == 0)
                    {
                        rowHandlers = null;
                        parent.ValueChanged -= RowChangedHandler;
                    }
                }
                else
                {
                    rowHandlers[index] = value;
                }
            }
        }
        private Dictionary<int, EventHandler> rowHandlers;
    }

    public class MatrixViewRow<T> : ICustomTypeDescriptor
    {
        private readonly int index;
        public int Index { get { return index; } }
        private readonly MatrixView<T> parent;
        internal MatrixViewRow(MatrixView<T> parent, int index)
        {
            if (index < 0) throw new ArgumentOutOfRangeException("index");
            parent.CheckNull("parent");
            this.index = index;
            this.parent = parent;
        }
        public override string ToString()
        {
            return parent.RowName(index);
        }
        #region ICustomTypeDescriptor Members
        private static Type RowType
        {
            get
            {
                return
                    typeof(MatrixViewRow<T>);
            }
        }
        AttributeCollection ICustomTypeDescriptor.GetAttributes()
        {
            return TypeDescriptor.GetAttributes(RowType);
        }
        string ICustomTypeDescriptor.GetClassName()
        {
            return TypeDescriptor.GetClassName(RowType);
        }
        string ICustomTypeDescriptor.GetComponentName()
        {
            return TypeDescriptor.GetComponentName(RowType);
        }
        TypeConverter ICustomTypeDescriptor.GetConverter()
        {
            return TypeDescriptor.GetConverter(RowType);
        }
        EventDescriptor ICustomTypeDescriptor.GetDefaultEvent()
        {
            return TypeDescriptor.GetDefaultEvent(RowType);
        }
        PropertyDescriptor ICustomTypeDescriptor.GetDefaultProperty()
        {
            return TypeDescriptor.GetDefaultProperty(RowType);
        }
        object ICustomTypeDescriptor.GetEditor(Type editorBaseType)
        {
            return TypeDescriptor.GetEditor(RowType, editorBaseType);
        }
        EventDescriptorCollection
   ICustomTypeDescriptor.GetEvents(Attribute[] attributes)
        {
            return TypeDescriptor.GetEvents(RowType, attributes);
        }
        EventDescriptorCollection ICustomTypeDescriptor.GetEvents()
        {
            return TypeDescriptor.GetEvents(RowType);
        }
        PropertyDescriptorCollection
   ICustomTypeDescriptor.GetProperties(Attribute[] attributes)
        {
            return ThisDesc.GetProperties();
        }
        private ICustomTypeDescriptor ThisDesc { get { return this; } }
        PropertyDescriptorCollection ICustomTypeDescriptor.GetProperties()
        {
            return parent.GetColumns();
        }
        object ICustomTypeDescriptor.GetPropertyOwner(PropertyDescriptor pd)
        {
            return this;
        }
        #endregion
    }

    internal class MatrixViewCol<T> : PropertyDescriptor
    {
        public override string ToString()
        {
            return Name;
        }
        private static readonly Attribute[] fixedAttribs = new Attribute[0];
        private readonly MatrixView<T> parent;
        private int index;
        public MatrixViewCol(MatrixView<T> parent, int index)
            : base(parent == null ? "None" : parent.ColName(index), fixedAttribs)
        {
            parent.CheckNull("parent");
            this.parent = parent;
            this.index = index;
        }
        public override Type ComponentType
        {
            get { return typeof(MatrixViewRow<T>); }
        }
        public override bool IsReadOnly
        {
            get { return false; }
        }
        public override Type PropertyType
        {
            get { return typeof(T); }
        }
        public override bool CanResetValue(object component)
        {
            return true;
        }
        public override void ResetValue(object component)
        {
            SetValue(component, default(T));
        }
        private MatrixViewRow<T> Row(object component)
        {
            return ((MatrixViewRow<T>)component);
        }
        public override void SetValue(object component, object value)
        {
            parent[index, Row(component).Index] = (T)value;
        }
        public override object GetValue(object component)
        {
            return parent[index, Row(component).Index];
        }
        public override bool ShouldSerializeValue(object component)
        {
            return
   !EqualityComparer<T>.Default.Equals((T)GetValue(component), default(T));
        }
        protected override object GetInvocationTarget(Type type, object
   instance)
        {
            return base.GetInvocationTarget(type, instance);
        }
        public override bool SupportsChangeEvents
        {
            get { return true; }
        }
        public override void AddValueChanged(object component, EventHandler
   handler)
        {
            parent.AddRowHandler(Row(component).Index, handler);
        }
        public override void RemoveValueChanged(object component,
   EventHandler handler)
        {
            parent.RemoveRowHandler(Row(component).Index, handler);
        }
    } 
}
